-
Notifications
You must be signed in to change notification settings - Fork 1.5k
bug:added restart dev button #2755
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
bug:added restart dev button #2755
Conversation
@Surajsuthar is attempting to deploy a commit to the Onlook Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughAdds a dev-server restart flow in two UI areas: introduces Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant UI as CodeControls / TerminalArea
participant Session as editorEngine.sandbox.session
participant Toast
User->>UI: Click "Restart Dev Server" button
UI->>UI: if isRestarting -> ignore
UI->>UI: set isRestarting = true (disable, show spinner)
UI->>Session: restartDevServer()
alt success
Session-->>UI: resolves
UI->>Toast: toast.success("Dev server restarted")
else failure/error
Session-->>UI: rejects / throws
UI->>Toast: toast.error("Failed to restart dev server")
end
UI->>UI: set isRestarting = false (enable, hide spinner)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (3)
apps/web/client/src/app/project/[id]/_components/right-panel/dev-tab/code-controls.tsx (3)
96-112
: Prevent floating promise; keep tooltip usable when disabled
- Add void before calling the async handler in onClick to satisfy no-floating-promises rules and keep consistency with saveFile usage.
- Optional: disabled buttons often don’t trigger tooltips. Wrapping the button with a span as the TooltipTrigger keeps the tooltip accessible when disabled (same improvement applies to other disabled icon buttons in this file as a follow-up).
Apply this diff:
- <TooltipTrigger asChild> - <Button - variant="ghost" - size="icon" - disabled={isRestarting} - onClick={() => handleRestartDevServer()} - className="p-2 w-fit h-fit hover:bg-background-onlook cursor-pointer" - aria-label="Restart dev server" - > - <Icons.Reload className={cn("h-4 w-4", isRestarting && "animate-spin")}/> - </Button> - </TooltipTrigger> + <TooltipTrigger asChild> + <span> + <Button + variant="ghost" + size="icon" + disabled={isRestarting} + onClick={() => void handleRestartDevServer()} + className="p-2 w-fit h-fit hover:bg-background-onlook cursor-pointer" + aria-label="Restart dev server" + > + <Icons.Reload className={cn("h-4 w-4", isRestarting && "animate-spin")} /> + </Button> + </span> + </TooltipTrigger>
15-15
: Importtoast
from the UI wrapper for consistent themingThe UI package’s
sonner.tsx
wrapper (packages/ui/src/components/sonner.tsx) re-exportstoast
from “sonner”, ensuring it flows through our design tokens/providers. Importing directly from"sonner"
bypasses our theming. Please update this file accordingly:• File:
apps/web/client/src/app/project/[id]/_components/right-panel/dev-tab/code-controls.tsx
Line 15:-import { toast } from 'sonner'; +import { toast } from '@onlook/ui/sonner';You may also want to run a repo-wide check for other direct
"sonner"
imports and replace them with@onlook/ui/sonner
for uniform styling.
41-55
: Harden restart handler: rename, debounce, enrich errorsA few optional tweaks to make this handler more robust and self-documenting. None are strictly required given the current API, but they’ll improve readability and fault-tolerance:
• Rename the response variable for consistency
- const reStartResponse = await editorEngine.sandbox.session.restartDevServer(); + const restartResponse = await editorEngine.sandbox.session.restartDevServer();—matches the
Promise<boolean>
return type and conventional camelCase.• Prevent concurrent restarts
if (isRestarting) return; setIsRestarting(true);—early-return stops double-clicks or repeated calls from queuing up.
• Simplify the boolean check
if (restartResponse) { toast.success('Dev server restart initiated'); } else { toast.error('Failed to restart dev server'); }Since
restartDevServer(): Promise<boolean>
always resolves totrue
orfalse
, you can drop theundefined
-handling branch.• Log and surface detailed errors
} catch (err) { console.error('restartDevServer failed', err); const message = err instanceof Error ? err.message : String(err); toast.error(`Error restarting dev server: ${message}`); } finally { setIsRestarting(false); }—captures stack traces in the console and shows the underlying message in the toast.
Overall, these are optional refactors to tighten up naming, prevent duplicate executions, and improve observability.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
apps/web/client/src/app/project/[id]/_components/right-panel/dev-tab/code-controls.tsx
(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/client/src/app/project/[id]/_components/right-panel/dev-tab/code-controls.tsx (5)
packages/ui/src/components/sonner.tsx (1)
toast
(19-19)packages/ui/src/components/tooltip.tsx (3)
Tooltip
(72-72)TooltipTrigger
(72-72)TooltipContent
(72-72)packages/ui/src/components/button.tsx (1)
Button
(57-57)packages/ui/src/components/icons/index.tsx (1)
Icons
(137-3592)apps/web/template/lib/utils.ts (1)
cn
(4-6)
🔇 Additional comments (2)
apps/web/client/src/app/project/[id]/_components/right-panel/dev-tab/code-controls.tsx (2)
25-25
: State flag looks goodUsing a simple boolean for in-flight state is appropriate here and aligns with the rest of the component.
59-59
: UI container tweaks LGTMThe alignment/gap/opacity transitions read well and preserve the existing hover affordance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (3)
apps/web/client/src/app/project/[id]/_components/right-panel/dev-tab/code-controls.tsx (3)
25-25
: Guard against ultra-fast double clicks with a ref, not just stateState updates are async; two rapid clicks before the re-render can slip past the
isRestarting
check. Add a ref as a synchronous, in-memory guard while keeping state for UI.-import { useState } from 'react'; +import { useRef, useState } from 'react'; @@ -const [isRestarting, setIsRestarting] = useState(false); +const [isRestarting, setIsRestarting] = useState(false); +const restartingRef = useRef(false);
96-113
: Minor: pass handler directly; expose busy state; cursor when disabled
- Pass the handler directly to avoid creating a new function per render.
- Advertise busy state to assistive tech (
aria-busy
).- Optionally swap the cursor when disabled for clearer affordance.
-<Button +<Button variant="ghost" size="icon" disabled={isRestarting} - onClick={() => handleRestartDevServer()} - className="p-2 w-fit h-fit hover:bg-background-onlook cursor-pointer" + onClick={handleRestartDevServer} + aria-busy={isRestarting} + className={cn( + "p-2 w-fit h-fit hover:bg-background-onlook", + isRestarting ? "cursor-not-allowed" : "cursor-pointer" + )} aria-label="Restart dev server" > <Icons.Reload className={cn("h-4 w-4", isRestarting && "animate-spin")}/> </Button>
41-56
: Add a brief test plan (and optional unit test) for the restart flowGiven this is user-triggered infra control, please add a lightweight test plan to the PR description and consider a unit test that mocks the engine and verifies UI feedback.
Suggested manual test steps:
- Open a project and trigger “Restart dev server”.
- Expect: button disables, icon spins, toast says “Restart requested”.
- While spinning, attempt multiple clicks: no additional calls should be made.
- Simulate a failure (make
restartDevServer
reject): expect error toast and re-enabled button.Optional Jest/RTL sketch:
// CodeControls.restart.test.tsx import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { CodeControls } from './code-controls'; jest.mock('@/components/store/editor', () => ({ useEditorEngine: () => ({ sandbox: { session: { restartDevServer: jest.fn().mockResolvedValue(true) } }, ide: { activeFile: { isDirty: false }, files: [], saveActiveFile: jest.fn() }, }), })); test('invokes restart once and disables while in-flight', async () => { render(<CodeControls />); const btn = await screen.findByRole('button', { name: /restart dev server/i }); fireEvent.click(btn); // second click should be ignored fireEvent.click(btn); await waitFor(() => expect(btn).toHaveAttribute('aria-busy', 'true')); // assert toast and re-enable happen await waitFor(() => expect(btn).toHaveAttribute('aria-busy', 'false')); });If you’d like, I can open a follow-up PR to add this test.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
apps/web/client/src/app/project/[id]/_components/right-panel/dev-tab/code-controls.tsx
(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/client/src/app/project/[id]/_components/right-panel/dev-tab/code-controls.tsx (2)
packages/ui/src/components/sonner.tsx (1)
toast
(19-19)packages/ui/src/components/icons/index.tsx (1)
Icons
(137-3592)
🔇 Additional comments (2)
apps/web/client/src/app/project/[id]/_components/right-panel/dev-tab/code-controls.tsx (2)
15-15
: LGTM: toast importImport looks correct and consistent with the UI package usage.
60-60
: LGTM: container spacing and hover behaviorThe added alignment and gap styles integrate well with adjacent controls.
const handleRestartDevServer = async () => { | ||
try { | ||
if (isRestarting) return; | ||
setIsRestarting(true); | ||
const restartResponse = await editorEngine.sandbox.session.restartDevServer(); | ||
if (restartResponse) { | ||
toast.success('Dev server restarting'); | ||
} else { | ||
toast.error('Failed to restart dev server'); | ||
} | ||
} catch (err) { | ||
toast.error('Error restarting dev server'); | ||
} finally { | ||
setIsRestarting(false); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Harden restart handler: atomic reentrancy guard, null-check engine path, better error detail
- Use the ref guard to prevent concurrent calls.
- Null-check
editorEngine?.sandbox?.session?.restartDevServer
to avoid runtime errors when the session is unavailable. - Log error details for debugging and surface a concise user-facing toast.
-const handleRestartDevServer = async () => {
- try {
- if (isRestarting) return;
- setIsRestarting(true);
- const restartResponse = await editorEngine.sandbox.session.restartDevServer();
- if (restartResponse) {
- toast.success('Dev server restarting');
- } else {
- toast.error('Failed to restart dev server');
- }
- } catch (err) {
- toast.error('Error restarting dev server');
- } finally {
- setIsRestarting(false);
- }
-}
+const handleRestartDevServer = async () => {
+ if (restartingRef.current) return; // fast re-entry guard
+ restartingRef.current = true;
+ setIsRestarting(true);
+ try {
+ const restartFn = editorEngine?.sandbox?.session?.restartDevServer;
+ if (typeof restartFn !== 'function') {
+ toast.error('Restart unavailable in this context');
+ return;
+ }
+ const ok = await restartFn();
+ if (ok) {
+ toast.success('Dev server restart requested');
+ } else {
+ toast.error('Failed to request dev server restart');
+ }
+ } catch (err) {
+ // Keep the toast concise, but log details for diagnostics
+ // eslint-disable-next-line no-console
+ console.error('Error restarting dev server', err);
+ const msg = err instanceof Error ? err.message : 'Unknown error';
+ toast.error(`Error restarting dev server: ${msg}`);
+ } finally {
+ setIsRestarting(false);
+ restartingRef.current = false;
+ }
+};
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
apps/web/client/src/app/project/[id]/_components/right-panel/dev-tab/code-controls.tsx
around lines 41 to 56, the restart handler lacks an atomic reentrancy guard,
doesn't null-check the nested engine path before invoking restartDevServer, and
swallows error details; add a useRef boolean (e.g., restartingRef) to serve as
the atomic guard (check and set it immediately at function start and clear in
finally), verify editorEngine?.sandbox?.session?.restartDevServer is a function
before awaiting it (return early with a user toast if not available), and catch
errors by logging detailed error info (console.error or app logger) while
showing a concise toast message to the user; ensure the ref and setIsRestarting
are cleared in finally so state is consistent.
Oh this is nice. Could we move the button to next to the Dev Server CLI on the bottom bar perhaps? @Surajsuthar |
@Kitenite sure if you have any specific design lmk |
![]() is this right place @Kitenite ? |
sorry for the late reply, i think the last one with a tooltip would work. Thanks @Surajsuthar! |
Screen.Recording.2025-08-29.at.11.36.00.AM.mov |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
apps/web/client/src/app/project/[id]/_components/bottom-bar/terminal-area.tsx
(3 hunks)apps/web/client/src/app/project/[id]/_components/right-panel/dev-tab/code-controls.tsx
(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/client/src/app/project/[id]/_components/right-panel/dev-tab/code-controls.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/client/src/app/project/[id]/_components/bottom-bar/terminal-area.tsx (5)
apps/web/client/src/components/store/editor/index.tsx (1)
useEditorEngine
(9-13)packages/ui/src/components/sonner.tsx (1)
toast
(19-19)packages/ui/src/components/tooltip.tsx (3)
Tooltip
(72-72)TooltipTrigger
(72-72)TooltipContent
(72-72)packages/ui/src/components/icons/index.tsx (1)
Icons
(137-3592)apps/web/template/lib/utils.ts (1)
cn
(4-6)
🔇 Additional comments (2)
apps/web/client/src/app/project/[id]/_components/bottom-bar/terminal-area.tsx (2)
10-10
: LGTM: toast importImport path and usage align with the UI kit.
29-44
: Consolidate restart flow with proper error logging and state handling
- Preserve the boolean return check (the API’s
restartDevServer()
returnstrue
/false
to signal success/failure).- In the
catch
block, log the error withconsole.error('restartDevServer failed:', err)
and use a singletoast.error('Failed to restart dev server')
for consistency.- Keep the
isRestarting
guard and ensuresetIsRestarting(false)
always runs—even if the component unmounts—by tracking mount state via auseRef
(optional).- Optionally, streamline the UI with
toast.promise(...)
.Apply a minimal diff:
- try { - if (isRestarting) return; - setIsRestarting(true); - const restartResponse = await editorEngine.sandbox.session.restartDevServer(); - if (restartResponse) { - toast.success('Dev server restarting'); - } else { - toast.error('Failed to restart dev server'); - } - } catch (err) { - toast.error('Error restarting dev server'); - } finally { - setIsRestarting(false); - } + try { + if (isRestarting) return; + setIsRestarting(true); + const success = await editorEngine.sandbox.session.restartDevServer(); + if (!success) { + throw new Error('no dev server task found'); + } + toast.success('Dev server restarting'); + } catch (err) { + console.error('restartDevServer failed:', err); + toast.error('Failed to restart dev server'); + } finally { + setIsRestarting(false); + }Likely an incorrect or invalid review comment.
|
||
export const TerminalArea = observer(({ children }: { children: React.ReactNode }) => { | ||
const editorEngine = useEditorEngine(); | ||
const terminalSessions = editorEngine.sandbox.session.terminalSessions; | ||
const activeSessionId = editorEngine.sandbox.session.activeTerminalSessionId; | ||
|
||
const [terminalHidden, setTerminalHidden] = useState(true); | ||
const [isRestarting, setIsRestarting] = useState(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid per-component restart flags; centralize in store
If there are multiple restart entry points (e.g., CodeControls), a local isRestarting
can allow concurrent restarts from other surfaces. Prefer a single observable flag on editorEngine.sandbox.session
(e.g., isRestartingDevServer
) or have restartDevServer()
self-gate reentry and expose status.
🤖 Prompt for AI Agents
In apps/web/client/src/app/project/[id]/_components/bottom-bar/terminal-area.tsx
around line 18, you currently use a per-component isRestarting local state which
allows concurrent restarts from other UI surfaces; replace it by relying on a
single centralized restart status (preferably an observable on
editorEngine.sandbox.session like isRestartingDevServer) or make
restartDevServer self-gate reentry and expose its status. Remove the useState
hook, read/observe the centralized flag (or the restartDevServer promise/status)
to render UI and disable controls, and ensure restartDevServer sets/clears that
shared flag (or returns a single in-flight promise) so multiple components
cannot trigger concurrent restarts.
<Tooltip> | ||
<TooltipTrigger asChild> | ||
<button | ||
onClick={() => handleRestartDevServer()} | ||
className="h-9 w-9 flex items-center justify-center hover:text-foreground-hover text-foreground-tertiary hover:bg-accent/50 rounded-md border border-transparent" | ||
> | ||
<Icons.Reload className={cn("h-4 w-4", isRestarting && "animate-spin")}/> | ||
</button> | ||
</TooltipTrigger> | ||
<TooltipContent sideOffset={5} hideArrow>Restart dev server</TooltipContent> | ||
</Tooltip> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Disable during restart + a11y + avoid extra closure
- Disable the control while restarting; add
aria-label
/aria-busy
for accessibility. - Pass the handler reference directly to avoid recreating a closure every render.
- If your Tooltip is Radix-based, disabled buttons won’t trigger tooltips; wrap the button with a
<span>
if you need the tooltip even when disabled.
- <TooltipTrigger asChild>
- <button
- onClick={() => handleRestartDevServer()}
- className="h-9 w-9 flex items-center justify-center hover:text-foreground-hover text-foreground-tertiary hover:bg-accent/50 rounded-md border border-transparent"
- >
- <Icons.Reload className={cn("h-4 w-4", isRestarting && "animate-spin")}/>
- </button>
- </TooltipTrigger>
+ <TooltipTrigger asChild>
+ <button
+ onClick={handleRestartDevServer}
+ disabled={isRestarting}
+ aria-label="Restart dev server"
+ aria-busy={isRestarting}
+ className={cn(
+ "h-9 w-9 flex items-center justify-center rounded-md border border-transparent",
+ "text-foreground-tertiary hover:text-foreground-hover hover:bg-accent/50",
+ "disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none"
+ )}
+ >
+ <Icons.Reload
+ className={cn("h-4 w-4", isRestarting && "animate-spin")}
+ aria-hidden="true"
+ />
+ </button>
+ </TooltipTrigger>
This also aligns with the PR description claiming the button is disabled during restart.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<Tooltip> | |
<TooltipTrigger asChild> | |
<button | |
onClick={() => handleRestartDevServer()} | |
className="h-9 w-9 flex items-center justify-center hover:text-foreground-hover text-foreground-tertiary hover:bg-accent/50 rounded-md border border-transparent" | |
> | |
<Icons.Reload className={cn("h-4 w-4", isRestarting && "animate-spin")}/> | |
</button> | |
</TooltipTrigger> | |
<TooltipContent sideOffset={5} hideArrow>Restart dev server</TooltipContent> | |
</Tooltip> | |
<Tooltip> | |
<TooltipTrigger asChild> | |
<button | |
onClick={handleRestartDevServer} | |
disabled={isRestarting} | |
aria-label="Restart dev server" | |
aria-busy={isRestarting} | |
className={cn( | |
"h-9 w-9 flex items-center justify-center rounded-md border border-transparent", | |
"text-foreground-tertiary hover:text-foreground-hover hover:bg-accent/50", | |
"disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none" | |
)} | |
> | |
<Icons.Reload | |
className={cn("h-4 w-4", isRestarting && "animate-spin")} | |
aria-hidden="true" | |
/> | |
</button> | |
</TooltipTrigger> | |
<TooltipContent sideOffset={5} hideArrow> | |
Restart dev server | |
</TooltipContent> | |
</Tooltip> |
Description
Added button to restart dev server
Related Issues
"bug #2749"
Type of Change
Testing
Screenshots (if applicable)
Screen.Recording.2025-08-25.at.2.38.38.PM.mov
Additional Notes
Important
Adds a "Restart dev server" button to
CodeControls
with loading state and user feedback usingtoast
.CodeControls
component incode-controls.tsx
.isRestarting
state.toast
based on restart outcome.isRestarting
state to manage button's disabled state and loading animation.This description was created by
for f7606a6. You can customize this summary. It will automatically update as commits are pushed.
Summary by CodeRabbit
New Features
UX/UI